/**
 * Copyright 2011 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.google.api.adwords.starterkit.examples.enhancedads;

import com.google.api.adwords.starterkit.StarterKit;
import com.google.api.adwords.starterkit.examples.enhancedads.settings.AccountSettings;
import com.google.api.adwords.starterkit.examples.enhancedads.settings.EmailSettings;
import com.google.api.adwords.starterkit.settings.AdGroupSettings;
import com.google.api.adwords.starterkit.settings.CampaignSettings;
import com.google.api.adwords.starterkit.settings.KeywordSettings;
import com.google.api.adwords.starterkit.settings.StarterKitSettings;
import com.google.api.adwords.starterkit.settings.TextAdSettings;
import com.google.api.adwords.starterkit.utils.DumpExceptions;
import com.google.api.adwords.starterkit.utils.JSONUtils;
import com.google.api.adwords.starterkit.utils.SettingsException;

import org.apache.log4j.Logger;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;

/**
 * Entry point class for Enhanced Ads Sample.
 * The Class EnhancedAds is a demo downloads a XML file and sync up linked ads 
 * with both products and categories, using their ids
 * Also, it makes possible to use extra configuration parameters, such as 
 * start and end dates for ads, days of the week to run the ad and so on.
 *  
 * @author rodrigo.f.vale@gmail.com  (Rodrigo Vale)
 */
public class EnhancedAds {
  private String delimiterTag;
  private String prodIdTag;
  private String prodNameTag;
  private String priceTag;
  private String regularPriceTag;
  private String catIdTag;
  private String catNameTag;
  private String parcelTag;
  private String parcelValueTag;
  private String unitsTag;
  private String urlTag;
  private String authorTag;
  private String imgTag;
  private String otherTag;
  private ArrayList<Collection> collections = new ArrayList<Collection>();
  private HashMap<String, AccountSettings> accounts = new HashMap<String, AccountSettings>(); 
  private Currency currency;
  private String xmlFile;
  private EmailSettings emailSettings;
  
  private String currencySign;
  private String decimalSeparator;
  private String thousandsSeparator;
  
  private String regexp;
  private int parcelGroup;
  private int parcelValueGroup;
  
  private StarterKitSettings starterKitSettings;
  
  private static final Logger log = Logger.getLogger("StarterKit");
  
  /**
   * Load the Collections tag from settings file and store its content at 
   * collections object. 
   */
  private void loadCollectionSettings(JSONObject jsonEnhancedAdWords) {
    JSONArray jsonCollections = 
        JSONUtils
            .getJSONArray(jsonEnhancedAdWords, "Collections", "Collection");
    for (int i = 0; i < jsonCollections.length(); i++) {
      JSONObject jsonCollection;
      try {
        jsonCollection = jsonCollections.getJSONObject(i);
        JSONArray jsonTags = JSONUtils.getJSONArray(jsonCollection, "Tag");
        Collection collection = new Collection();
        collection.setName(jsonCollection.getString("name"));        
        for (int t = 0; t < jsonTags.length(); t++) {
          CollectionItem collectionItem 
              = CollectionItem.fromJSON(jsonTags.get(t), currency);
          collection.getItems().add(collectionItem);
        }
        collections.add(collection);
      } catch (JSONException e) {
        DumpExceptions.dumpJSONException(e);
      }
    }
  }
  
  /**
   * Load the Currency tag from settings file and store its content at 
   * currency object. 
   */
  private void loadCurrencySettings(JSONObject jsonEnhancedAdWords) {
    try {
      currencySign = jsonEnhancedAdWords.getJSONObject("currency")
          .getString("sign");
      decimalSeparator = jsonEnhancedAdWords.getJSONObject("currency")
          .getString("decimal_separator");
      thousandsSeparator = jsonEnhancedAdWords.getJSONObject("currency")
          .getString("thousands_separator");
      
      currency = new Currency(currencySign, decimalSeparator,
          thousandsSeparator);
    } catch (JSONException e) {
      DumpExceptions.dumpJSONException(e);
    }
  }
  
  /**
   * Load the Accounts tag from settings file and store its content at 
   * accounts object. 
   */
  private void loadAccountsSettings(JSONObject jsonEnhancedAdWords,
      String commandLineXmlFile) {

    if (commandLineXmlFile.length() > 0) {
      String login = starterKitSettings.getLogin();
      String password 
        = starterKitSettings.getPassword();
      String clientLogin
        = starterKitSettings.getClientLogin();
      
      AccountSettings account = new AccountSettings(login, password, clientLogin, 
          commandLineXmlFile);
      String key = account.getKey();

      getAccounts().put(key, account);
    }
    
    try {
      JSONArray jsonAccounts = JSONUtils
          .getJSONArray(jsonEnhancedAdWords, "Accounts", "Account");
      for (int i = 0; i < jsonAccounts.length(); i++) {
        JSONObject jsonAccount = jsonAccounts.getJSONObject(i);
        AccountSettings account = AccountSettings.fromJSON(jsonAccount);
        String key = account.getKey();        
        if (!getAccounts().containsKey(key)) {
          getAccounts().put(key, account);
        }
      }
    } catch (JSONException e) {
      DumpExceptions.dumpJSONException(e);
    }
  }
  
  /**
   * Load the Email tag from settings file and store its content at 
   * email object. 
   */
  private void loadEmailSettings(JSONObject jsonEnhancedAdWords) {
    try {
      JSONObject jsonEmail = 
          JSONUtils.getJSONObject(jsonEnhancedAdWords, "Email");
      JSONObject jsonAccount = 
          JSONUtils.getJSONObject(jsonEmail, "Account");

      String host = 
        JSONUtils.getJSONString(jsonAccount, "Host", "");
      int smtpPort = 
        JSONUtils.getJSONInt(jsonAccount, "SmtpPort", 0);
      String login = 
        JSONUtils.getJSONString(jsonAccount, "Login", "");
      String password = 
        JSONUtils.getJSONString(jsonAccount, "Password", "");
      String from = 
          JSONUtils.getJSONString(jsonEmail, "From", "");
      String attachFile = 
        JSONUtils.getJSONString(jsonEmail, "AttachFile", "./report.csv");
      String title = 
        JSONUtils.getJSONString(jsonEmail, "Title", "");
      String content = 
        JSONUtils.getJSONString(jsonEmail, "Content", "");
      boolean tls = 
        JSONUtils.getJSONBoolean(jsonAccount, "TLS", false);
      
      JSONArray jsonDestinations = JSONUtils
          .getJSONArray(jsonEmail, "Destinations", "Destination");

      HashSet<String> destinations = new HashSet<String>();
      for (int i = 0; i < jsonDestinations.length(); i++) {
        String destination = jsonDestinations.getString(i);
        destinations.add(destination);
      }
      
      emailSettings = new EmailSettings(host, smtpPort, login, password, from,
          tls, destinations, attachFile, title, content);
    } catch (JSONException e) {
      DumpExceptions.dumpJSONException(e);
    }
  }  
  
  /**
   * Load the Parcel tags from settings file and store its content to 
   * parcelVariables. 
   */
  private void loadParcelSettings(JSONObject jsonEnhancedAdWords) {
    try {      
      JSONObject jsonParcel = 
          JSONUtils.getJSONObject(jsonEnhancedAdWords, "parcelTag");
      if (jsonParcel == null) {
        parcelTag = jsonEnhancedAdWords.getString("parcelTag");
        regexp = "";
      } else {
        parcelTag = jsonParcel.getString("content");
        // check for the attributes regexp, parcel and parcelValue
        if (jsonParcel.has("regexp")) {
          regexp = jsonParcel.getString("regexp"); 
        } else {
          regexp = "";
        }
        if (jsonParcel.has("parcel")) {
          String sParcelGroup = jsonParcel.getString("parcel");
          sParcelGroup = sParcelGroup.replaceAll("[^\\d]", "");
          parcelGroup = Integer.parseInt(sParcelGroup); 
        } else {
          parcelGroup = 0;
        }
        if (jsonParcel.has("parcelValue")) {
          String sParcelValueGroup = jsonParcel.getString("parcelValue");
          sParcelValueGroup = sParcelValueGroup.replaceAll("[^\\d]", "");
          parcelValueGroup = Integer.parseInt(sParcelValueGroup); 
        } else {
          parcelValueGroup = 0;
        }
      }
      
      parcelValueTag = jsonEnhancedAdWords.getString("parcelValueTag");
    } catch (JSONException e) {
      DumpExceptions.dumpJSONException(e);
    }
  }
  
  /**
   * Make sure that Enhanced Ads will not touch in any other campaign settings.
   */
  private void setAutoCreationFlags(StarterKitSettings starterKitSettings) {
    CampaignSettings cpSettings = starterKitSettings.getCampaignSettings();
    cpSettings.setAutoCreation(false);
    cpSettings.setAutoPause(false);
    cpSettings.setCanModifyBudget(false);
    cpSettings.setCanModifyStatus(false);
    
    AdGroupSettings agSettings = starterKitSettings.getAdGroupSettings();
    agSettings.setAutoCreation(false);
    agSettings.setAutoPause(false);
    agSettings.setBidModifiable(false);
    agSettings.setStatusModifiable(false);
    
    KeywordSettings kwSettings = starterKitSettings.getKeywordSettings();
    kwSettings.setAutoCreation(false);
    kwSettings.setAutoPause(false);
    kwSettings.setCanModifyBid(false);
    kwSettings.setCanModifyStatus(false);
    kwSettings.setCanModifyUrl(false);
    
    TextAdSettings txSettings = starterKitSettings.getTextAdSettings();
    txSettings.setAutoCreation(false);
    txSettings.setAutoPause(false);
    txSettings.setSyncStatus(false);    
  }
  
  /**
   * Loads the settings.xml file. The user can define a single account to be
   * processed at the standard StarterKit AdWordsAPI tag in the settings.xml
   * file. Or the user can define a multiple accounts to be processed at the 
   * EnhancedAds Accounts tag in the settings.xml. Using the standard 
   * AdWordsAPI tag, the user should define at command line level the full 
   * XML path that contains the product catalog. 
   */
  private void loadSettings(String settingsFile, String commandLineXmlFile) {
    setStarterKitSettings(new StarterKitSettings(settingsFile));
    
    setAutoCreationFlags(getStarterKitSettings());
    
    // it does not make sense to work with deleted campaigns
    getStarterKitSettings().setDownloadDeletedCampaigns(false);

    JSONObject jsonSettings = getStarterKitSettings().getJSONSettings();
    try {
      JSONObject jsonEnhancedAdWords 
          = jsonSettings.getJSONObject("EnhancedAdWords");
      
      loadCurrencySettings(jsonEnhancedAdWords);
      loadCollectionSettings(jsonEnhancedAdWords);
      loadParcelSettings(jsonEnhancedAdWords);      
      loadAccountsSettings(jsonEnhancedAdWords, commandLineXmlFile);
      loadEmailSettings(jsonEnhancedAdWords);
      
      delimiterTag = jsonEnhancedAdWords.getString("DelimiterTag");
      prodIdTag = jsonEnhancedAdWords.getString("prodIdTag");
      prodNameTag = jsonEnhancedAdWords.getString("prodNameTag");
      priceTag = jsonEnhancedAdWords.getString("priceTag");
      regularPriceTag = jsonEnhancedAdWords.getString("regularPriceTag");
      catIdTag = jsonEnhancedAdWords.getString("catIdTag");
      catNameTag = jsonEnhancedAdWords.getString("catNameTag");

      unitsTag = jsonEnhancedAdWords.getString("unitsTag");
      urlTag = jsonEnhancedAdWords.getString("urlTag");
      authorTag = jsonEnhancedAdWords.getString("authorTag");
      imgTag = jsonEnhancedAdWords.getString("imgTag");
      otherTag = jsonEnhancedAdWords.getString("otherTag");      
    } catch (JSONException e) {
      DumpExceptions.dumpJSONException(e);
    }    
  }
  
  /**
   * Execute the EnhancedAds application for the specific account
   */
  public void execute(AccountSettings account) {
    StarterKit starterKit = new StarterKit(getStarterKitSettings());
    
    log.info("Processing account: login: " + starterKitSettings.getLogin() 
        + " ClientLogin: " + starterKitSettings.getClientLogin());
    
    starterKit.load();
    
    Connection conn = starterKit.getConnection();    
    try {
      conn.setAutoCommit(false);    
    } catch (SQLException e) {
      DumpExceptions.dumpSQLException(e);      
    }     
    AdWords2DB adWords2DB = new AdWords2DB(conn);
    adWords2DB.extractURLsParams();
    
    XML2DB xml2DB = new XML2DB(conn, delimiterTag, prodNameTag, priceTag, 
        regularPriceTag, prodIdTag, catIdTag, catNameTag, parcelTag, regexp,
        parcelGroup, parcelValueGroup, parcelValueTag, unitsTag, urlTag, 
        authorTag, imgTag, otherTag, currency, collections);
  
    File file = new File(xmlFile);    
    xml2DB.parser(file);
    
    DB2AdWords db2AdWords = new DB2AdWords(conn, currency, account, 
        emailSettings);
    
    db2AdWords.updateTextAds();
    try {
      conn.commit();
      conn.setAutoCommit(true);    
    } catch (SQLException e) {
      DumpExceptions.dumpSQLException(e);      
    }     
    starterKit.save();
    starterKit.done();    
  }
  
  public void downLoad(String source, String fileOutput) {    
    xmlFile = fileOutput;
    log.info("Downloading source: " + source + " to " + fileOutput);
    
    URL url = null;
    try {
      url = new URL(source);
    } catch (MalformedURLException e) {
      log.error("Error processing the Source URL");
      log.error(e.getMessage());
    }
    ReadableByteChannel rbc = null;
    try {
      rbc = Channels.newChannel(url.openStream());
    } catch (IOException e) {
      log.error("Error creating new Channel");
      log.error(e.getMessage());
    }
    FileOutputStream fos = null;
    try {
      fos = new FileOutputStream(xmlFile);
    } catch (FileNotFoundException e) {
      log.error("Error creating internal cache xml file");
      log.error(e.getMessage());
    }
    try {
      fos.getChannel().transferFrom(rbc, 0, 1 << 30);
    } catch (IOException e) {
      log.error("Error downloading XML file " + source);
      log.error(e.getMessage());
    } finally {
      if (fos != null) {
        try {
          fos.close();
        } catch (IOException e) {
          DumpExceptions.dumpIOException(e);
        }
      }
    }    
  }
  
  public static void howToUse() {
    System.out.print(
        "SYNOPSIS\n" +
        "       EnhancedAdwords [SETTINGS] [XML FILE]\n" +
        "\n" +
        "DESCRIPTION\n" +
        "       EnhancedAdwords is a powerfull example to allow you to " +
        "syncronize your AdWords accounts with your inventory of products." +
        "Based on a XML feed and tags on DestUrl of your textAds, the " +
        "application can pause/unpause textads.\n" +
        "\n" +
        "EXAMPLE\n" +
        "       java starterkit.jar AdTemplateExample settings.xml " +
        "[http|https|ftp|file]://products.xml\n\n" +
        "       products.xml is optional, you can define a list of accounts\n" +
        "to be processed by EnhancedAds using the Accounts tag in\n" + 
        "settings.xml file\n");
    System.exit(0);
  }
  public static void main(String[] args) {
    if ((args.length == 0) || (args.length > 2)) {
      howToUse();
    }
    
    EnhancedAds enhancedAdwords = new EnhancedAds();
    
    if (args.length == 2) {
      enhancedAdwords.loadSettings(args[0], args[1]);
    }
    
    if (args.length == 1) {
      enhancedAdwords.loadSettings(args[0], "");
    }    
    
    HashMap<String, AccountSettings> accounts = enhancedAdwords.getAccounts();
    
    Iterator<String> i = accounts.keySet().iterator();
    while (i.hasNext()) {
      String key = (String) i.next();
      AccountSettings account = accounts.get(key);
      
      String login = account.getLogin();
      String password = account.getPassword();
      String clientLogin = account.getClientEmail();
      
      enhancedAdwords.getStarterKitSettings().setLogin(login);
      enhancedAdwords.getStarterKitSettings().setPassword(password);
      enhancedAdwords.getStarterKitSettings().setClientLogin(clientLogin);
      enhancedAdwords.getStarterKitSettings().setDbName(account.getKey());
      
      String outputFile = enhancedAdwords.getStarterKitSettings().getCacheDir() 
          + "/" + account.MD5(account.getXml()) + "_" + "products.xml";
      
      enhancedAdwords.downLoad(account.getXml(), outputFile);
      enhancedAdwords.execute(account);
    }
  }

  public void setAccounts(HashMap<String, AccountSettings> accounts) {
    this.accounts = accounts;
  }

  public HashMap<String, AccountSettings> getAccounts() {
    return accounts;
  }

  public void setStarterKitSettings(StarterKitSettings starterKitSettings) {
    this.starterKitSettings = starterKitSettings;
  }

  public StarterKitSettings getStarterKitSettings() {
    return starterKitSettings;
  }  
}